/*****************************************
 * Extractor.java
 * ----------------
 * Revision: v.1.0
 * Author: Ji Kim
 * Date Created: 12/13/2009
 * Description:
 * Last Back-Up:
 * Revision History:
 *****************************************/

import java.awt.*;
import java.io.IOException;
import java.util.Random;
import java.util.Vector;
import java.util.Enumeration;
import javax.media.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.datasink.*;
import javax.media.control.MonitorControl;
import java.io.*;
import java.net.*;

public class Extractor implements BufferTransferHandler {

	DataSource ds;
	PushBufferStream streams[];
	Buffer buff;
	LiveStream ls;
	Buffer bufftemp;
	DatagramSocket sock;
	InetAddress destIP;
	double drate;
	int destPort;
	int counter = 1;
	byte[] one;
	byte[] two;
	byte[] three;
	
	Random rgen;

	//Constructor
	public Extractor(DataSource src, InetAddress ip, int port){
		//DataSource containing the digitized audio stream is a push-type stream
		streams = ((PushBufferDataSource)src).getStreams();

		// There's only a single audio stream in the DataSource
		streams[0].setTransferHandler(this);

		ds = src;
		buff = new Buffer();
		bufftemp = new Buffer();

		ls = new LiveStream();

		try{
			sock = new DatagramSocket();
		}catch(SocketException se){
			System.err.println("Failed to open socket.");
		}

		destIP = ip;
		destPort = port;
		
		drate = 0.0;
		rgen = new Random();

		System.out.println("::Destination Host identified as: " + destIP + "/" + port);
	}

	public LiveStream getLiveStream(){
		return ls;
	}

	public PushBufferStream getStream(){
		return streams[0];
	}

	public void start() {
		try {
			ds.start();
		} catch (IOException e) {
			System.err.println(e);
		}
	}

	public void stop() {
		try {
			ds.stop();
		} catch (IOException e) {
			System.err.println(e);
		}
	}

	/** This will get called when there's data pushed from the PushBufferDataSource. **/
	public void transferData(PushBufferStream stream) {
		try {
			stream.read(buff);
		} catch (IOException e) {
			System.err.println(e);
			return;
		}
		transmit(buff);
	}
	
	//Set the packet loss rate as a percentage.
	public void setDropRate(double rate){
		drate = rate;
		System.out.println("::Set Packet Loss Rate to: " + drate);
	}
	
	//Returns true if packet isn't to be dropped, false if it should be dropped.
	private boolean checkDrop(){
		double roll = rgen.nextDouble();
		System.out.println("::DEBUG, Packet Loss Rolled: " + roll);
		
		if(roll>drate)
			return true;
		else
			return false;
	}

	//Executes every time there's data available to be sent in the PushBufferStream.
	//Used to extract each Buffer in the stream and convert it to byte[] for transmission.
	void transmit(Buffer buffer) {
		/** Extracted Buffer/byte[] Debug Messages **/
		/*
		System.out.println("::DEBUG, Extracted Buffer Data:");
		System.out.println("  *Time Stamp: " + buffer.getTimeStamp());
		System.out.println("  *Sequence #: " + buffer.getSequenceNumber());
		System.out.println("  *Pertinent Data Length: " + buffer.getLength());
		System.out.println("  *Data Format: " + buffer.getFormat());
		System.out.println("  *Header: " + buffer.getHeader()); //32864
		System.out.println("  *Flags: " + buffer.getFlags());
		System.out.println("  *Actual Data Length: " + ((byte[])buffer.getData()).length);
		System.out.println("  *Offset to Pertinent Data: " + buffer.getOffset());
		
		System.out.println("  *Full Data: ");
		byte[] temp = (byte[])buffer.getData();
		for(int i=0;i<temp.length;i++){
			System.out.print(temp[i]+" ");
		}System.out.println("");
		*/

		/** TRANSMIT CODE **/
		byte[] toSend = (byte[])buffer.getData();
		// NON-ENCODED TRANSMISSION
		/*
		try{
			//Use socket to send data block to destination IP.
			sock.send(new DatagramPacket(toSend,0,toSend.length,destIP,destPort));
			//System.out.println("::DEBUG, Sent Packet...");
		}catch(Exception e){
			System.err.println("Failed to send packet, SeqNo: "+buffer.getSequenceNumber());
		}
		*/
		
		// DIVERSITY ENCODED TRANSMISSION: (2,3,2) Test Scheme
		if(counter==1){
			one = toSend;

			byte[] withHeader = new byte[toSend.length+1];
			System.arraycopy(toSend,0,withHeader,1,toSend.length);
			withHeader[0]=(byte)counter;
			counter++;
			try{
				sock.send(new DatagramPacket(withHeader,0,withHeader.length,destIP,destPort));
				//System.out.println("Sent Packet...");
			}catch(Exception e){
				System.err.println("Failed to send packet, SeqNo: "+buffer.getSequenceNumber());
			}
		}
		else{
			three = new byte[toSend.length];
			two = toSend;
			for(int i=0;i<toSend.length;i++){
				three[i]=(byte)(one[i]^two[i]);
				//System.out.println("Pkt:"+buffer.getSequenceNumber()+" "+three[i]);
			}
			byte[] withHeader = new byte[toSend.length+1];
			System.arraycopy(toSend,0,withHeader,1,toSend.length);
			withHeader[0]=(byte)counter;
			counter++;
			try{
				sock.send(new DatagramPacket(withHeader,0,withHeader.length,destIP,destPort));
				//System.out.println("Sent Packet...");
			}catch(Exception e){
				System.err.println("Failed to send packet, SeqNo: "+buffer.getSequenceNumber());
			}
			withHeader = new byte[three.length+1];
			System.arraycopy(three,0,withHeader,1,three.length);
			withHeader[0]=(byte)counter;
			counter=1;
			try{
				sock.send(new DatagramPacket(withHeader,0,withHeader.length,destIP,destPort));
				//System.out.println("Sent Packet...");
			}catch(Exception e){
				System.err.println("Failed to send packet, SeqNo: "+buffer.getSequenceNumber());
			}
		}
		
		
		/** Testing locally, build stream from "received" packets. **/
		/*
		byte[] holder = new byte[buffer.getLength()];
		System.arraycopy((byte[])buffer.getData(), 0, holder, 0, buffer.getLength());
		if(checkDrop()){
			ls.update(holder);
		}
		else
			System.out.println("::DEBUG, Packet Dropped!");
		*/
		/** Testing locally, received stream debug messages. **/
		/*
		try{
			ls.read(bufftemp);
		}catch(IOException e){
			System.err.println(e);
		}
		System.out.println("::DEBUG, Retrieved Data Stats:");
		System.out.println("  *Time Stamp: " + bufftemp.getTimeStamp());
		System.out.println("  *Seq Number: " + bufftemp.getSequenceNumber());
		System.out.println("  *Pertinent Data Length: " + bufftemp.getLength());
		System.out.println("  *Data Format: " + bufftemp.getFormat());
		System.out.println("  *Actual Data Length: " + (byte[])bufftemp.getData());
		System.out.println("  *Full Data: ");
		byte[] rtemp = (byte[])bufftemp.getData();
		for(int i=0;i<rtemp.length;i++){
			System.out.print(rtemp[i]+" ");
		}System.out.println("");
		
		System.out.println("::DEBUG, Checking for Equivalence with Sent Data:");
		for(int i=0;i<temp.length;i++){
			if(temp[i]!=rtemp[i])
				System.out.println("  *FAILED");
			else
				System.out.println("  *PASSED");
		}
		*/
	}

}

